"""
Configuration management for GCR Solver Manager.
"""

import os
from pathlib import Path
from typing import Dict, Any, Optional, Union
import yaml
from pydantic import BaseModel, Field, validator
from dotenv import load_dotenv

from .exceptions import ConfigurationError
from .utils import get_project_root


class DatabaseConfig(BaseModel):
    """Database configuration."""
    
    url: str = Field(default="sqlite:///gcr_solver.db", description="Database URL")
    echo: bool = Field(default=False, description="Enable SQL query logging")
    pool_size: int = Field(default=5, description="Connection pool size")
    max_overflow: int = Field(default=10, description="Max pool overflow")
    pool_timeout: int = Field(default=30, description="Pool timeout in seconds")
    pool_recycle: int = Field(default=3600, description="Pool recycle time in seconds")


class BuildConfig(BaseModel):
    """Build configuration."""
    
    source_dir: str = Field(default="../", description="Source directory relative to project root")
    build_dir: str = Field(default="build", description="Build directory")
    parallel_jobs: int = Field(default=4, description="Number of parallel build jobs")
    cmake_generator: str = Field(default="Unix Makefiles", description="CMake generator")
    default_gpu_type: str = Field(default="cuda", description="Default GPU type (cuda/hip)")
    build_timeout: int = Field(default=1800, description="Build timeout in seconds")
    cache_enabled: bool = Field(default=True, description="Enable build caching")
    
    @validator('default_gpu_type')
    def validate_gpu_type(cls, v):
        if v not in ['cuda', 'hip']:
            raise ValueError('GPU type must be either "cuda" or "hip"')
        return v


class TestConfig(BaseModel):
    """Test configuration."""
    
    default_nproc: int = Field(default=4, description="Default number of MPI processes")
    default_nprocx: int = Field(default=2, description="Default nprocx parameter")
    default_resolution: float = Field(default=1.0, description="Default resolution")
    test_timeout: int = Field(default=3600, description="Test timeout in seconds")
    slurm_enabled: bool = Field(default=True, description="Enable SLURM integration")
    slurm_partition: str = Field(default="gpu", description="Default SLURM partition")
    slurm_account: Optional[str] = Field(default=None, description="SLURM account")
    remote_enabled: bool = Field(default=False, description="Enable remote execution")
    remote_host: Optional[str] = Field(default=None, description="Remote host")
    remote_user: Optional[str] = Field(default=None, description="Remote username")
    remote_workdir: Optional[str] = Field(default=None, description="Remote working directory")


class LogConfig(BaseModel):
    """Log configuration."""
    
    log_dir: str = Field(default="logs", description="Log directory")
    retention_days: int = Field(default=30, description="Log retention in days")
    compression_enabled: bool = Field(default=True, description="Enable log compression")
    compression_age_days: int = Field(default=7, description="Compress logs older than N days")
    max_log_size: int = Field(default=100*1024*1024, description="Max log file size in bytes")
    real_time_monitoring: bool = Field(default=True, description="Enable real-time log monitoring")


class AnalysisConfig(BaseModel):
    """Analysis configuration."""
    
    norm_tolerance: float = Field(default=1e-10, description="Norm comparison tolerance")
    performance_baseline_days: int = Field(default=7, description="Performance baseline period in days")
    trend_analysis_enabled: bool = Field(default=True, description="Enable trend analysis")
    anomaly_detection_enabled: bool = Field(default=True, description="Enable anomaly detection")
    report_output_dir: str = Field(default="reports", description="Report output directory")


class APIConfig(BaseModel):
    """API configuration."""
    
    host: str = Field(default="0.0.0.0", description="API host")
    port: int = Field(default=8000, description="API port")
    reload: bool = Field(default=False, description="Enable auto-reload")
    debug: bool = Field(default=False, description="Enable debug mode")
    cors_origins: list = Field(default=["*"], description="CORS allowed origins")
    jwt_secret: Optional[str] = Field(default=None, description="JWT secret key")
    jwt_algorithm: str = Field(default="HS256", description="JWT algorithm")
    jwt_expiration: int = Field(default=86400, description="JWT expiration in seconds")


class Config(BaseModel):
    """Main configuration class."""
    
    # Environment
    environment: str = Field(default="development", description="Environment (development/production)")
    debug: bool = Field(default=True, description="Debug mode")
    
    # Project paths
    project_root: str = Field(default_factory=lambda: str(get_project_root()))
    
    # Subsystem configurations
    database: DatabaseConfig = Field(default_factory=DatabaseConfig)
    build: BuildConfig = Field(default_factory=BuildConfig)
    test: TestConfig = Field(default_factory=TestConfig)
    log: LogConfig = Field(default_factory=LogConfig)
    analysis: AnalysisConfig = Field(default_factory=AnalysisConfig)
    api: APIConfig = Field(default_factory=APIConfig)
    
    # Logging
    log_level: str = Field(default="INFO", description="Log level")
    log_format: str = Field(
        default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        description="Log format"
    )
    
    @validator('environment')
    def validate_environment(cls, v):
        if v not in ['development', 'testing', 'production']:
            raise ValueError('Environment must be one of: development, testing, production')
        return v
    
    @validator('log_level')
    def validate_log_level(cls, v):
        valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
        if v.upper() not in valid_levels:
            raise ValueError(f'Log level must be one of: {valid_levels}')
        return v.upper()
    
    @classmethod
    def load_from_file(cls, config_file: Optional[Union[str, Path]] = None) -> "Config":
        """
        Load configuration from YAML file.
        
        Args:
            config_file: Path to configuration file. If None, uses default config files.
            
        Returns:
            Config instance
            
        Raises:
            ConfigurationError: If configuration loading fails
        """
        # Load environment variables
        load_dotenv()
        
        config_data = {}
        
        if config_file:
            # Load specific config file
            config_path = Path(config_file)
            if not config_path.exists():
                raise ConfigurationError(f"Configuration file not found: {config_path}")
            
            try:
                with open(config_path, 'r') as f:
                    config_data = yaml.safe_load(f) or {}
            except yaml.YAMLError as e:
                raise ConfigurationError(f"Failed to parse YAML configuration: {e}")
        else:
            # Load default configuration files
            project_root = get_project_root()
            config_dir = project_root / "config"
            
            # Load base configuration
            default_config = config_dir / "default.yaml"
            if default_config.exists():
                try:
                    with open(default_config, 'r') as f:
                        config_data = yaml.safe_load(f) or {}
                except yaml.YAMLError as e:
                    raise ConfigurationError(f"Failed to parse default configuration: {e}")
            
            # Load environment-specific configuration
            env = os.getenv('GSM_ENVIRONMENT', 'development')
            env_config = config_dir / f"{env}.yaml"
            if env_config.exists():
                try:
                    with open(env_config, 'r') as f:
                        env_data = yaml.safe_load(f) or {}
                        config_data = cls._merge_config(config_data, env_data)
                except yaml.YAMLError as e:
                    raise ConfigurationError(f"Failed to parse environment configuration: {e}")
        
        # Override with environment variables
        config_data = cls._apply_environment_overrides(config_data)
        
        try:
            return cls(**config_data)
        except Exception as e:
            raise ConfigurationError(f"Failed to create configuration: {e}")
    
    @staticmethod
    def _merge_config(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
        """
        Recursively merge configuration dictionaries.
        
        Args:
            base: Base configuration
            override: Override configuration
            
        Returns:
            Merged configuration
        """
        result = base.copy()
        
        for key, value in override.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = Config._merge_config(result[key], value)
            else:
                result[key] = value
        
        return result
    
    @staticmethod
    def _apply_environment_overrides(config_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Apply environment variable overrides to configuration.
        
        Environment variables should be prefixed with GSM_ and use underscores
        to separate nested keys. For example:
        - GSM_DATABASE_URL for database.url
        - GSM_BUILD_PARALLEL_JOBS for build.parallel_jobs
        
        Args:
            config_data: Base configuration data
            
        Returns:
            Configuration with environment overrides applied
        """
        env_prefix = "GSM_"
        
        for env_var, value in os.environ.items():
            if not env_var.startswith(env_prefix):
                continue
            
            # Convert environment variable to config path
            config_path = env_var[len(env_prefix):].lower().split('_')
            
            # Navigate to the correct location in config
            current = config_data
            for key in config_path[:-1]:
                if key not in current:
                    current[key] = {}
                current = current[key]
            
            # Set the value (try to convert to appropriate type)
            final_key = config_path[-1]
            try:
                # Try to parse as JSON for complex types
                import json
                current[final_key] = json.loads(value)
            except (json.JSONDecodeError, ValueError):
                # If JSON parsing fails, try common type conversions
                if value.lower() in ('true', 'false'):
                    current[final_key] = value.lower() == 'true'
                elif value.isdigit():
                    current[final_key] = int(value)
                elif '.' in value and value.replace('.', '').isdigit():
                    current[final_key] = float(value)
                else:
                    current[final_key] = value
        
        return config_data
    
    def get_absolute_path(self, relative_path: str) -> Path:
        """
        Convert relative path to absolute path based on project root.
        
        Args:
            relative_path: Relative path string
            
        Returns:
            Absolute Path object
        """
        if Path(relative_path).is_absolute():
            return Path(relative_path)
        return Path(self.project_root) / relative_path
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert configuration to dictionary."""
        return self.dict()
    
    def save_to_file(self, file_path: Union[str, Path]) -> None:
        """
        Save configuration to YAML file.
        
        Args:
            file_path: Path to save configuration
        """
        file_path = Path(file_path)
        file_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(file_path, 'w') as f:
            yaml.dump(self.to_dict(), f, default_flow_style=False, sort_keys=False)


# Global configuration instance
_config: Optional[Config] = None


def get_config() -> Config:
    """
    Get the global configuration instance.
    
    Returns:
        Global Config instance
    """
    global _config
    if _config is None:
        _config = Config.load_from_file()
    return _config


def set_config(config: Config) -> None:
    """
    Set the global configuration instance.
    
    Args:
        config: Config instance to set as global
    """
    global _config
    _config = config


def reload_config() -> Config:
    """
    Reload the global configuration from files.
    
    Returns:
        Reloaded Config instance
    """
    global _config
    _config = Config.load_from_file()
    return _config